﻿using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

namespace InfiniTec.Security.Certificates
{
    public class SelfSignedCertificate
    {
        public static X509Certificate2 Create(CertificateInfo certificateInfo)
        {
            byte[] encodedName;
            uint cbName = 0;
            string keyContainer = Guid.NewGuid().ToString();

            if (SafeNativeMethods.CertStrToName(X509Encoding.AsnString, certificateInfo.DistinguishedName,
                                                X509Encoding.X500NameString | X509Encoding.ReverseRdns, IntPtr.Zero,
                                                null, ref cbName, IntPtr.Zero))
            {
                encodedName = new byte[cbName];
                SafeNativeMethods.CertStrToName(X509Encoding.AsnString, certificateInfo.DistinguishedName,
                                                X509Encoding.X500NameString | X509Encoding.ReverseRdns, IntPtr.Zero,
                                                encodedName, ref cbName, IntPtr.Zero);
            }
            else
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }


            var subjectblob = new CERT_NAME_BLOB {pbData = Marshal.AllocHGlobal(encodedName.Length)};
            Marshal.Copy(encodedName, 0, subjectblob.pbData, encodedName.Length);
            subjectblob.cbData = encodedName.Length;

            IntPtr context;

            bool contextAcquired = SafeNativeMethods.CryptAcquireContext(out context, keyContainer,
                                                                         certificateInfo.ProviderName,
                                                                         CryptographicProvider.RsaFull,
                                                                         KeyContainerOptions.CreateKeyset |
                                                                         KeyContainerOptions.UseMachineKeySet);
            if (!contextAcquired)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            IntPtr signKey;

            SysTime start = ConvertDateTimeToSysTime(certificateInfo.StartDate);
            SysTime end = ConvertDateTimeToSysTime(certificateInfo.EndDate);

            if (!SafeNativeMethods.CryptGenKey(context, KeySpecification.KeyExchange, 0, out signKey))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }


            var pInfo = new CRYPT_KEY_PROV_INFO
                            {
                                pwszContainerName = keyContainer,
                                pwszProvName = certificateInfo.ProviderName,
                                dwProvType = certificateInfo.Provider,
                                dwFlags = CspOptions.KeepHandleOpen,
                                dwKeySpec = certificateInfo.KeySpecification
                            };

            var extensions = new CertExtensions();
            IntPtr hCertCntxt = SafeNativeMethods.CertCreateSelfSignCertificate(
                context, ref subjectblob, 0, pInfo, IntPtr.Zero, start, end, ref extensions);

            IntPtr certificateCopy;


            if (!SafeNativeMethods.CertAddCertificateContextToStore(
                     certificateInfo.TargetStore.StoreHandle, hCertCntxt, 3 /*CERT_STORE_ADD_REPLACE_EXISTING*/,
                     out certificateCopy))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            SafeNativeMethods.CertFreeCertificateContext(hCertCntxt);

            pInfo = new CRYPT_KEY_PROV_INFO
                        {
                            pwszContainerName = keyContainer,
                            pwszProvName = certificateInfo.ProviderName,
                            dwProvType = CryptographicProvider.RsaFull,
                            dwFlags = CspOptions.UseMachineKeyset,
                            dwKeySpec = KeySpecification.KeyExchange
                        };

            if (!SafeNativeMethods.CertSetCertificateContextProperty(certificateCopy, 2, 0, pInfo))
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }

            var result = new X509Certificate2(certificateCopy);

            SafeNativeMethods.CryptDestroyKey(signKey);

            if (hCertCntxt == IntPtr.Zero)
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }


            Marshal.FreeHGlobal(subjectblob.pbData);


            SafeNativeMethods.CryptReleaseContext(context, 0);

            return result;
        }

        private static
            SysTime
            ConvertDateTimeToSysTime(DateTime? date)
        {
            if (date == null) return null;

            var result = new SysTime
                             {
                                 wDay = ((ushort) date.Value.Day),
                                 wDayOfWeek = ((ushort) date.Value.DayOfWeek),
                                 wMonth = ((ushort) date.Value.Month),
                                 wYear = ((ushort) date.Value.Year),
                                 wHour = ((ushort) date.Value.Hour),
                                 wMinute = ((ushort) date.Value.Minute),
                                 wSecond = ((ushort) date.Value.Second),
                                 wMilliseconds = ((ushort) date.Value.Millisecond)
                             };

            return result;
        }
    }
}